﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using D2DEngine.Core;
using D2DEngine.Components;
using D2DEngine.Tile;
using FarseerPhysics.Dynamics;

namespace D2DEngine.Scene
{
    public class D2DScene
    {
        protected List<D2DSceneObject> sceneObjects;
        protected List<D2DSceneObject> addedObjects;
        protected List<D2DSceneObject> deletedObjects;

        protected List<D2DSceneComponent> components;

        protected World world;

        protected Rectangle boundingBox = Rectangle.Empty;

        protected D2DCamera camera = null;

        /// <summary>
        /// The scene camera
        /// </summary>
        public D2DCamera Camera
        {
            get { return this.camera; }
            set { this.camera = value; }
        }

        /// <summary>
        /// The scene physical world 
        /// </summary>
        public World World
        {
            get { return this.world; }
            set { this.world = value; }
        }

        /// <summary>
        /// The world (scene) bound rectangle
        /// </summary>
        public Rectangle BoundingBox
        {
            get { return this.boundingBox; }
            set { this.boundingBox = value; }
        }

        /// <summary>
        /// The scene objects in the scene
        /// </summary>
        public List<D2DSceneObject> SceneObjects
        {
            get { return this.sceneObjects; }
        }

        public D2DScene(World world)
        {
            this.camera = new D2DCamera();

            this.Clear();

            this.world = world;
        } 

        public D2DScene()
        {
            this.camera = new D2DCamera();

            this.Clear();

            this.world = new World(Vector2.Zero);
        }

        /// <summary>
        /// Wipes the scene but keeps the World.
        /// </summary>
        public void Clear()
        {
            this.sceneObjects = new List<D2DSceneObject>();
            this.addedObjects = new List<D2DSceneObject>();
            this.deletedObjects = new List<D2DSceneObject>();
            this.components = new List<D2DSceneComponent>();
        }
        

        /// <summary>
        /// Returns the object found.
        /// </summary>
        /// <typeparam name="T">The unique name of the object</typeparam>
        /// <returns></returns>
        public T FindObject<T>(string name) where T : D2DSceneObject
        {
            T foundObject = default(T);
            foreach (D2DSceneObject obj in this.sceneObjects)
            {
                if (obj.Name == name)
                {
                    foundObject = obj as T;

                    if (foundObject != null)
                    {
                        return foundObject;
                    }
                }
            }

            foreach (D2DSceneObject obj in this.addedObjects)
            {
                if (obj.Name == name)
                {
                    foundObject = obj as T;

                    if (foundObject != null)
                    {
                        return foundObject;
                    }
                }
            }

            return default(T);
        }


        /// <summary>
        /// Registers an object to the scene
        /// </summary>
        /// <param name="obj"></param>
        public void Register(D2DSceneObject obj)
        {            
            D2DSceneObject mountedObject;
            D2DMountComponent mount = obj.Mount;

            if (mount != null)
            {
                foreach (string key in mount.AttachedObjects.Keys)
                {
                    mountedObject = mount.AttachedObjects[key].SceneObject;
                    this.addedObjects.Add(mountedObject);
                    mountedObject.OnRegister();
                }
            }
            
            this.addedObjects.Add(obj);

            obj.Scene = this;
            obj.OnRegister();
        }

        /// <summary>
        /// Unregister an object to the scene
        /// </summary>
        /// <param name="obj">The object to unregister</param>
        public void UnRegister(D2DSceneObject obj)
        {
            obj.OnUnRegister();
            this.sceneObjects.Remove(obj);            
        }

        public List<D2DSceneObject> Collect(Vector2 position, float radius)
        {
            List<D2DSceneObject> collectedObjects = new List<D2DSceneObject>();
            BoundingSphere boundingSphere = new BoundingSphere(new Vector3(position, 1), radius);

            foreach (D2DSceneObject sceneObject in this.sceneObjects)
            {
                if (boundingSphere.Contains(new Vector3(sceneObject.Position, 1)) == ContainmentType.Contains)
                {
                    collectedObjects.Add(sceneObject);
                }
            }

            return collectedObjects;
        }

        public List<D2DSceneObject> Collect(BoundingSphere boundingSphere)
        {
            List<D2DSceneObject> collectedObjects = new List<D2DSceneObject>();

            foreach (D2DSceneObject sceneObject in this.sceneObjects)
            {
                if (boundingSphere.Contains(new Vector3(sceneObject.Position, 1)) == ContainmentType.Contains)
                {
                    collectedObjects.Add(sceneObject);
                }
            }

            return collectedObjects;
        }

        public List<D2DSceneObject> Collect(Rectangle rectangle)
        {
            List<D2DSceneObject> collectedObjects = new List<D2DSceneObject>();

            foreach (D2DSceneObject sceneObject in this.sceneObjects)
            {
                if (rectangle.Contains((int)sceneObject.Position.X, (int)sceneObject.Position.Y))
                {
                    collectedObjects.Add(sceneObject);
                }
            }

            return collectedObjects;
        }

        /// <summary>
        /// Updates the scene
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            // Update the camera
            this.camera.Update(gameTime);

            // Update the physics world
            this.world.Step((float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.001f);

            // Update all components
            foreach (D2DSceneComponent component in this.components)
            {
                if (component.IsRegistered)
                    component.Update(gameTime);
            }

            // Add the dynamically registered objects to the scene objects list
            foreach (D2DSceneObject tempObject in this.addedObjects)
            {
                this.sceneObjects.Add(tempObject);
            }

            // Clear the list containing recently added objects
            this.addedObjects.Clear();

            // Update the scene objects
            foreach (D2DSceneObject sceneObject in this.sceneObjects)
            {
                // if the object is alive
                if (sceneObject.IsRegistered)                
                    sceneObject.Update(gameTime);
                else
                    this.deletedObjects.Add(sceneObject);                
            }

            // Unregister disposed objects
            foreach (D2DSceneObject sceneObject in this.deletedObjects)
                UnRegister(sceneObject);

            // Clear the list containing the recently deleted objects
            this.deletedObjects.Clear();
        }

        /// <summary>
        /// Draws all of the objects in the scene
        /// </summary>
        /// <param name="spriteBatch"></param>
        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, this.camera.TransformMatrix);

            // Draw all scene objects
            foreach (D2DSceneObject sceneObject in this.sceneObjects)
            {
                sceneObject.Draw(spriteBatch);
            }

            // Draw all components
            foreach (D2DSceneComponent component in this.components)
            {
                component.Draw(spriteBatch);
            }

            spriteBatch.End();
        }


        /// <summary>
        /// Adds a component to the object
        /// </summary>
        /// <param name="component"></param>
        public void AddComponent(D2DSceneComponent component)
        {
            component.Owner = this;
            component.OnRegister();

            this.components.Add(component);
        }

        /// <summary>
        /// Returns a component of specified type.
        /// </summary>
        /// <typeparam name="T">Type of component</typeparam>
        /// <returns></returns>
        public T GetComponent<T>() where T : D2DSceneComponent
        {
            T local = default(T);
            foreach (D2DSceneComponent component in this.components)
            {
                local = component as T;
                if (local != null)
                {
                    return local;
                }
            }
            return default(T);
        }

    }
}
